#include"raytracer.h"

void RayTracer::set_scene(Scene* _scene) { scene = _scene; }
void RayTracer::set_camera(Camera* _cam) { cam = _cam; }
RayTracer::RayTracer(IntegrationType integration_type, bool enableMedia)
	:integration_type(integration_type), scene(NULL), cam(NULL), enableMedia(enableMedia) {}
IntegrationType RayTracer::get_tracer_type() { return integration_type; }

Spectrum RayTracer::sampleDirectLighting(SurfaceInteraction* si, shared_ptr<BSDF> bsdf, Ray &ray, Light* light, RandGenerator *randGen, bool isInMedia, bool processSpecular)
{
	//Direct lighting, PBR book Chap.14.3
	//multiple importance sampling (light + bsdf)
	Spectrum Ld;
	BxDFType requiredType = processSpecular ? BSDF_ALL : BxDFType(BSDF_ALL^BSDF_SPECULAR);

	//if (ray.o.y >= 1.98) {
	//	fprintf(stdout, "lookhere!");
	//}
	//part I: sample from light
	P3 lightDir;
	Float lightPdf = 0, scatteringPdf = 0;
	VisibilityTester vis;
	Float rands[] = { randGen->genRand(),randGen->genRand() };
	Spectrum light_at_interaction = light->sampleLightPt(ray.o, &lightDir, rands, &lightPdf, &vis);
	vis.medium = static_cast<Object*>(si->pObj)->mi.getMedium(si->n, -lightDir);
	//if (surface_n.dot(lightDir) < 0)
	//	vis.medium = ray.medium;
	//else
	//	vis.medium = scene->objects[objectID]->mi.inside;
	//vis.setMedium(bsdf->w, ray.medium, scene->objects[objectID]->medium.get());
	if (lightPdf > 0 && !light_at_interaction.IsBlack())
	{
		Spectrum f;

		if (ray.medium&&isInMedia)
		{
			//sampling media interaction

			//get scattering pdf using phase function
			scatteringPdf = ray.medium->scatterPdf(-ray.d, -lightDir);
			f = Spectrum(scatteringPdf);
		}
		else
		{
			//sampling surface interaction
			f = bsdf->f(-ray.d, -lightDir, requiredType)*abs(si->n.dot(lightDir));
			scatteringPdf = bsdf->getPdf(-ray.d, -lightDir, requiredType);

		}
		if (!f.IsBlack())
		{
			//do visibility test
			if (enableMedia)
			{
				light_at_interaction *= vis.Tr(scene, static_cast<Object*>(si->pObj), randGen);
			}
			else
			{
				if (!vis.unoccluded(scene, static_cast<Object*>(si->pObj)))
					light_at_interaction = Spectrum();
			}
			if (!light_at_interaction.IsBlack())
			{
				if (isDeltaLight(*light))
					Ld += f * light_at_interaction / lightPdf;
				else
				{
					//Float weight = 1;
					Float weight = PowerHeuristic(1, lightPdf, 1, scatteringPdf);
					Ld += weight * f*light_at_interaction / lightPdf;
				}
			}
		}
	}

	//part II: sample from BSDF
	return Ld;
	//if (isDeltaLight(*light)) return Ld;
	rands[0] = randGen->genRand(), rands[1] = randGen->genRand();
	Spectrum f;
	P3 reflDir;
	bool sampleSpec = false;
	if (ray.medium&&isInMedia)
	{
		//sample medium scattering
		reflDir = ray.medium->sample_inScatterDir(ray.o, -ray.d,
			&scatteringPdf, rands);
		f = Spectrum(scatteringPdf);
	}
	else
	{
		//sample surface scattering described by bsdf
		BxDFType sampledType;
		f = bsdf->ImportanceSample_f(-ray.d, &reflDir, rands,
			&scatteringPdf, requiredType, &sampledType);
		f *= abs(si->n.dot(reflDir));
		sampleSpec = (sampledType & BSDF_SPECULAR) != 0;
	}
	if (!f.IsBlack() && scatteringPdf > 0)
	{
		Float weight = 1;
		if (!sampleSpec)
		{
			lightPdf = light->getLightPdf(ray.o, reflDir);
			if (lightPdf == 0)return Ld;
			weight = PowerHeuristic(1, scatteringPdf, 1, lightPdf);
		}

		Ray ray_scatter(ray.o, reflDir, ray.medium);
		ray_scatter.medium = static_cast<Object*>(si->pObj)->mi.getMedium(si->n,reflDir);
		Spectrum attenuation(1.f);
		Float isect_t;
		SurfaceInteraction isect_si;
		bool intersectSurface = enableMedia ? scene->intersectMedia(ray_scatter, &isect_t, &isect_si, &attenuation, randGen) :
			scene->nearest_intersect(ray_scatter, &isect_t, &isect_si);

		Spectrum light_scatter;
		if (intersectSurface)
		{
			int lightID = static_cast<Object*>(isect_si.pObj)->lightID;
			if (lightID>=0&&scene->lights[lightID] == light)
				light_scatter = static_cast<Object*>(isect_si.pObj)->lightOut(&isect_si, reflDir);
		}
		else
		{
			light_scatter = light->lightEnv(ray_scatter);
		}
		if (!light_scatter.IsBlack())
			Ld += weight * f*light_scatter*attenuation / scatteringPdf;
	}
	return Ld;
}
